/*!
 * Chrome Extension - CSPAT
 * background.js - v1.0.0
 * Created by Jing (2014-2-18 11:49:22)
 *
 */


/**
* Define a basic logger functions
*/

var mstr = {};
mstr.debugging = false;
mstr.$log = function log(methodName, msg) {
    console.log("[Log] " + methodName + " : " + msg);
};
mstr.$warning = function warning(methodName, msg) {
    console.log("[Warning] " + methodName + " : " + msg);
};
mstr.$error = function error(methodName, msg) {
    console.log("[Error] " + methodName + " : " + msg);
};
mstr.$debug = function debug(methodName, msg) {
    if (mstr.debugging) {
        console.log("[Debug] " + methodName + " : " + msg);
    }
};


function STDataManager() {
    this.result = {};
    this.isStatsReady = false;
    this.isDataReady = false;

    return this;
}

STDataManager.prototype.reset = function() {
    this.result = {};
    this.isStatsReady = false;
    this.isDataReady = false;
};

STDataManager.prototype.getResult = function () {
    return this.result;
};

/*STDataManager.prototype.setStartTime = function (startTime) {
    if(!this.result.statistics) {
        this.result.statistics = {};
    }
    if(!this.result.statistics.startTime) {
        this.result.statistics.startTime = startTime;
    }
};*/

STDataManager.prototype.modifyActionStartTime = function(request) {
    if(request.time) {
        if (!this.result.statistics) {
            this.result.statistics = {};
        }

        if(this.result.statistics.startTime) {
            if(this.result.statistics.startTime > request.time) {
                this.result.statistics.startTime = request.time;
            }
        }
        else {
            this.result.statistics.startTime = request.time;
        }
    }
}

// Reset the end time of current action;
STDataManager.prototype.modifyActionEndTime = function (request) {
    var _methodName = "modifyActionEndTime";
    // Modify end time only after resources downloading finished.
    if (request.time) {
        if (!this.result.statistics) {
            this.result.statistics = {};
        }
        if (request.duration) {
            this.result.statistics.endTime = request.time + request.duration;
            this.result.statistics.total = this.result.statistics.endTime - this.result.statistics.startTime;
        }
        else {
            this.result.statistics.endTime = request.time;
            this.result.statistics.total = this.result.statistics.endTime - this.result.statistics.startTime;
        }
    }
};

// Handle a xhr request of different readyStates.
// Caution: Since a xhr request doesn't have an id, we need handle it separately.
STDataManager.prototype.addXhrRequest = function (request) {
    var _methodName = "addXhrRequest";

    //Find out the request in result.xhr if any that has the same url.
    var isExisted = false;
    var resReq;
    if (this.result.xhr) {
        for (var i in this.result.xhr) {
            if (request.data.url == this.result.xhr[i].url) {
                isExisted = true;
                resReq = this.result.xhr[i];
            }
        }
    }
    else {
        this.result.xhr = [];
    }
    //Handle the request.
    switch (request.data.readyState) {
        case 0:
            break;
        case 1:
            break;
        case 2:
            //If already exists, count++;otherwise, add it to the list.
            if (isExisted) {
                resReq.count++;
            }
            else {
                resReq = {};
                resReq.url = request.data.url;
                resReq.type = "XHR";
                resReq.count = 1;
                resReq.startTime = request.time;
                this.result.xhr.push(resReq);
            }
            break;
        case 3:
            break;
        case 4:
            if (isExisted) {
                resReq.endTime = request.time;
                this.modifyActionEndTime(request);
            }
            else {
                mstr.$error(_methodName, "Request not found. Url:" + request.data.url);
            }
            break;
        default:
            mstr.$error(_methodName, "Unrecognized XHR state: " + request.data.readyState);
            break;
    }
};

// Scan response headers and add statistics to result if any.
STDataManager.prototype.addHeaderStatistics = function (request) {
    if (!this.result.iserver) {
        this.result.iserver = [];
    }
    if (!this.result.web) {
        this.result.web = [];
    }
    var headerValues = request.data.response.headers;
    var url = request.data.response.url;
    var iServerTime = headerValues[HEADER_NAME_ISERVER_TOTAL];
    var webServerTime = headerValues[HEADER_NAME_WEB_APP];
    if(iServerTime) {
        this.result.iserver.push({url:url, elapsed:iServerTime});
    }
    if(webServerTime) {
        this.result.web.push({url:url, elapsed:webServerTime});
    }
};

// Scan html text for a specific statistic.
STDataManager.prototype.scanTextForStat = function (statText, statPattern, statsObj, statName) {
    if (!statText || !statPattern || !statsObj || !statName) {
        return false;
    }

    // Try to match the pattern against the supplied stat text...
    var results = statText.match(statPattern);
    if (results) {
        statsObj[statName] = parseInt(results[1], 10);
        return true;
    }
    return false;
};

//Scan HTML for all statistics.
STDataManager.prototype.scanHTMLForStats = function (content) {
    var _methodName = "scanHTMLForStats";
    var statsObj, statsFound = false;
    // Check for a non-null DOM document...
    if (!content) {
        return null;
    }

    var parser = new DOMParser();
    var domDoc = parser.parseFromString(content, "application/xml");
    var node = domDoc.getElementById("dhtml_statistics");
    if (node) {
        //mstr.$log(_methodName, "Find.");
    }

    // Create an object to hold our stats...
    statsObj = {};
    statsFound = this.scanTextForStat(content, REGEX_ISrvrStat, statsObj, 'isrvrTime') || statsFound;
    statsFound = this.scanTextForStat(content, REGEX_WebStat, statsObj, 'webTime') || statsFound;
    statsFound = this.scanTextForStat(content, REGEX_WebTransStat, statsObj, 'webTransTime') || statsFound;
    statsFound = this.scanTextForStat(content, REGEX_TotalXMLAPICalls, statsObj, 'xmlapiTotal') || statsFound;
    statsFound = this.scanTextForStat(content, REGEX_PollingXMLAPICalls, statsObj, 'xmlapiPolling') || statsFound;
    statsFound = this.scanTextForStat(content, REGEX_BytesSent, statsObj, 'bytesSent') || statsFound;
    statsFound = this.scanTextForStat(content, REGEX_BytesRecv, statsObj, 'bytesReceived') || statsFound;
    if (!statsFound) {
        statsObj = null;
    }
    return statsObj;
};

// Add statistics to result.
STDataManager.prototype.addHtmlStatistics = function (innerHtml) {
    var _methodName = "addHtmlStatistics";
    var statsObj = this.scanHTMLForStats(innerHtml);
    if(!this.result.statistics) {
        this.result.statistics = {};
    }
	if (!statsObj) {
		mstr.$error(_methodName, "Statistics not found.");
		this.isStatsReady = true;
		return;
	}
    this.result.statistics.isrvr = statsObj.isrvrTime;
    this.result.statistics.web = statsObj.webTime;
    this.result.statistics.rsrc = statsObj.webTransTime;
    this.result.statistics.total = this.result.statistics.total || 0;
    this.result.statistics.startTime = this.result.statistics.startTime || 0;

    this.isStatsReady = true;
};

//Query next timeline event json data.
STDataManager.prototype.nextJsonObject = function (curr_offset, data) {
    var curr = curr_offset;
    var curr_state = STATE_START;
    var quote_type;
    var curr_jso = [];
    for (var start = 0; curr < data.length; curr++) {
        var curr_char = data.charAt(curr);
        curr_jso.push(curr_char);

        switch (curr_state) {
            case STATE_START:
                // drop any previously encountered characters preceding '{'
                var nested_level = 0;
                if (curr_char == "{") {
                    curr_state = STATE_IN_JSO;
                } else {
                    curr_jso = [];
                }
                break;

            case STATE_IN_JSO:
                if (curr_char == "'") {
                    curr_state = STATE_IN_QUOTE;
                    quote_type = "'";
                } else if (curr_char == '"') {
                    curr_state = STATE_IN_QUOTE;
                    quote_type = '"';
                } else if (curr_char == '{') {
                    nested_level++;
                } else if (curr_char == '}') {
                    if (nested_level < 0) {
                        throw new Error("Nested level should be > 0");
                    }
                    nested_level--;
                    if (nested_level == -1) {
                        return [curr + 1, curr_jso.join("")];
                    }
                }
                break;

            case STATE_IN_QUOTE:
                if (curr_char == "\\") {
                    curr++;
                    curr_char = data.charAt(curr);
                    curr_jso.push(curr_char);
                } else if (curr_char == quote_type) {
                    curr_state = STATE_IN_JSO;
                }
                break;
        }
    }
    // All valid records should have been spit out from STATE_IN_JSO
    return null;
};
STDataManager.prototype.processRecordObj = function (recordObj) {
    this.process(recordObj);
    if (recordObj.children) {
        for (var i in recordObj.children) {
            this.processRecordObj(recordObj.children[i]);
        }
    }
}
STDataManager.prototype.dataProcess = function (data) {
    var _methodName = "dataProcess";
    var json_result = [0];
    while (true) {
        var json_result = this.nextJsonObject(json_result[0], data);
        if (json_result == null) {
            break;
        }
        var recordStr = json_result[1];
        if (!/^\s*$/.test(recordStr)) {
            var recordObj = JSON.parse(recordStr);
            this.processRecordObj(recordObj);
        }
    }
    this.isDataReady = true;
};

/**
 * Browser Timeline Events Processors
 */

//An event representing the creation and dispatch of a DOM or Window level event (like a Click or DOMContentLoaded).
STDataManager.prototype.domEventProcessor = function (request) {
    var _methodName = "domEventProcessor";
};

//Indicates layout or reflow of the document.
STDataManager.prototype.layoutProcessor = function (request) {
    this.modifyActionEndTime(request);
};

//The renderer recalculated CSS styles. Style rules were rematched against the appropriate DOM elements.
STDataManager.prototype.recalStylesProcessor = function (request) {
    this.modifyActionEndTime(request);
};

//Indicates that some or part of the document was rasterized to the screen.
STDataManager.prototype.paintProcessor = function (request) {
    this.modifyActionEndTime(request);
};

//Indicates that the HTML tokenizer processed some of the document.
STDataManager.prototype.parseHTMLProcessor = function (request) {
    this.modifyActionEndTime(request);
};

//Indicates that a timer was created through either a call to setTimeout() or setInterval(). This event should always be zero duration.
STDataManager.prototype.timerInstallProcessor = function (request) {};

//Indicates that a timer was cancelled. This event should always be zero duration.
STDataManager.prototype.timerClearProcessor = function (request) {};

//Event corresponding to a timer fire.
STDataManager.prototype.timerProcessor = function (request) {};

//Indicates an XMLHttpRequest readystatechange event handler.
STDataManager.prototype.xhrReadyStateChangeProcessor = function (request) {
    var _methodName = "xhrReadyStateChangeProcessor";
    //If readyState not found return directly.
    if (!request.data || !request.data.readyState) {
        mstr.$error(_methodName, "'request.data.readyState' doesn't exist");
        return;
    }
    this.addXhrRequest(request);
};

//Indicates an XMLHttpRequest load event handler.
STDataManager.prototype.xhrLoadProcessor = function (request) {
    this.modifyActionEndTime(request);
};

//Indicates that a <script> tag has been encountered, evaluated/compiled and run. In the case of <script src="http://website.com/...">, this event should also cover the time to download the script.
STDataManager.prototype.evalScriptProcessor = function (request) {
    // Filter out extension scripts.
    if (!request.data.url.match(REGEX_JS_PARTTERN)) {
        return;
    }
    if (!this.result.js) {
        this.result.js = [];
    }
    if (request.duration != 0) {
        var jsReq = {};
        jsReq.startTime = request.time;
        jsReq.duration = request.duration;
        jsReq.scriptName = request.data.url;
        jsReq.scriptLine = request.data.lineNumber;
        this.result.js.push(jsReq);
        this.modifyActionEndTime(request);

        if (!this.result.statistics) {
            this.result.statistics = {};
        }
        if (!this.result.statistics.js) {
            this.result.statistics.js = 0;
        }
        this.result.statistics.js += request.duration;
    }
};

//Indicates that there was a call to console.markTimeline() from within javascript on the monitored page.
STDataManager.prototype.logEventProcessor = function (request) {};

//Indicates a network request is enqueued.
STDataManager.prototype.networkResStartProcessor = function (request) {
    var _methodName = "networkResStartProcessor";
    if (!this.result.resources) {
        this.result.resources = [];
    }

    var isExisted = false;
    var resReq;
    for (var i in this.result.resources) {
        resReq = this.result.resources[i];
        if (resReq.id == request.data.requestId) {
            isExisted = true; // Request is found.
            resReq.startTime = request.time;
            resReq.method = request.data.requestMethod || "";
            if(resReq.responseStartTime) {
                resReq.requestTime = resReq.responseStartTime - resReq.startTime;
            }
            if(resReq.endTime) {
                resReq.totalTime = resReq.endTime - resReq.startTime;
            }
        }
    }

    if(!isExisted) {
        resReq = {};
        resReq.id = request.data.requestId;
        resReq.url = request.data.url;
        resReq.startTime = request.time;
        resReq.method = request.data.requestMethod || "";

        this.result.resources.push(resReq);
    }

    this.modifyActionStartTime(request);
};

//Indicates that the renderer has started receiving bits from the resource loader. Note that this is NOT a network level time, but rather the timing from the perspective of the UI thread in the renderer. They usually align with network level timings, but if the UI thread is blocked doing work, this callback can be delayed.
STDataManager.prototype.networkResResponseProcessor = function (request) {
    var _methodName = "networkResResponseProcessor";
    if (!this.result.resources) {
        this.result.resources = [];
    }

    var isExisted = false;
    var resReq;
    for (var i in this.result.resources) {
        resReq = this.result.resources[i];
        if (resReq.id == request.data.requestId) {
            isExisted = true; // Request is found.
            resReq.responseStartTime = request.time;

            resReq.requestTime = resReq.responseStartTime - resReq.startTime;
            if(resReq.endTime) {
                resReq.responseTime = resReq.endTime - resReq.responseStartTime;
            }
        }
    }

    // Request should be found in the result, if not, we still push it in the queue with an error log.
    if(!isExisted) {
        resReq = {};
        resReq.id = request.data.requestId;
        resReq.responseStartTime = request.time;

        this.result.resources.push(resReq);

        mstr.$error(_methodName, "Request(id:" + resReq.id + ") is not found.");
    }
};

//Indictes a resource load is successful and complete.
STDataManager.prototype.networkResFinishedProcessor = function (request) {
    var _methodName = "networkResFinishedProcessor";
    if (!this.result.resources) {
        this.result.resources = [];
    }

    var isExisted = false;
    var resReq;
    for (var i in this.result.resources) {
        resReq = this.result.resources[i];
        if (resReq.id == request.data.requestId) {
            isExisted = true; // Request is found.
            resReq.endTime = request.time;

            resReq.responseTime = resReq.endTime - resReq.responseStartTime;
            resReq.totalTime = resReq.endTime - resReq.startTime;
        }
    }

    // Request should be found in the result, if not, we still push it in the queue with an error log.
    if(!isExisted) {
        resReq = {};
        resReq.id = request.data.requestId;
        resReq.endTime = request.time;

        this.result.resources.push(resReq);

        mstr.$error(_methodName, "Request(id:" + resReq.id + ") is not found.");
    }

    this.modifyActionEndTime(request);
};

//This event covers the time spent running JavaScript during an event dispatch
STDataManager.prototype.jsCallbackProcessor = function (request) {
    // Filter out extension scripts.
    if (!request.data.scriptName.match(REGEX_JS_PARTTERN)) {
        return;
    }

    if (!this.result.js) {
        this.result.js = [];
    }
    if (request.duration != 0) {
        var jsReq = {};
        jsReq.startTime = request.time;
        jsReq.duration = request.duration;
        jsReq.scriptName = request.data.scriptName;
        jsReq.scriptLine = request.data.scriptLine;
        this.result.js.push(jsReq);
        this.modifyActionEndTime(request);

        if (!this.result.statistics) {
            this.result.statistics = {};
        }
        if (!this.result.statistics.js) {
            this.result.statistics.js = 0;
        }
        this.result.statistics.js += request.duration;

    }
};

//This is a parent event for processing data for any resource loaded from the resource loader (e.g. HTML pages, images, external scripts).
STDataManager.prototype.resDataRecProcessor = function (request) {
    var _methodName = "resDataRecProcessor";
    //mstr.$warning(_methodName, "Remember me!!");
    if (!this.result.statistics) {
        this.result.statistics = {};
    }
    if (!this.result.statistics.rsrc) {
        this.result.statistics.rsrc = 0;
    }
    this.result.statistics.rsrc = request.time + request.duration - this.result.statistics.startTime;
};

//Indicates some time was spent doing garbage collection.
STDataManager.prototype.gcEventProcessor = function (request) {};

//Mark that the main resource DOM document finished parsing.
STDataManager.prototype.markDOMContentProcessor = function (request) {
    var _methodName = "markDOMContentProcessor";
};

//Mark that the main resource finished loading.
STDataManager.prototype.markLoadEventProcessor = function (request) {};

/**
 * Speed Tracer Event Processors
 */

//A resource request was scheduled to be added to the network queue.
STDataManager.prototype.schResReqProcessor = function (request) {};

//There was a change in the location bar or title of the tab. Can use to detect a page transition
STDataManager.prototype.tabChangedProcessor = function (request) {};

//Record that updates information for a given network resource. These messages are sent intermittenly as the resource loader learns more information about a given network resource. Resource updates have a series of optional fields.
STDataManager.prototype.netResUpdateProcessor = function (request) {
    var _methodName = "netResUpdateProcessor";
};

//Network message indicating a request for a resource is about to go out.
STDataManager.prototype.netReqWBSentProcessor = function (request) {
    var _methodName = "netReqWBSentProcessor";
    // This is a similar but more detailed processor as "networkResStartProcessor" (Event type 12).
    if (!this.result.resources) {
        this.result.resources = [];
    }

    var isExisted = false;
    var resReq;
    for (var i in this.result.resources) {
        resReq = this.result.resources[i];
        if (resReq.id == request.data.requestId) {
            isExisted = true; // Request is found.
            resReq.startTime = request.time;
            resReq.method = request.data.request.method || "";
            if(resReq.responseStartTime) {
                resReq.requestTime = resReq.responseStartTime - resReq.startTime;
            }
            if(resReq.endTime) {
                resReq.totalTime = resReq.endTime - resReq.startTime;
            }
        }
    }

    if(!isExisted) {
        resReq = {};
        resReq.id = request.data.requestId;
        resReq.url = request.data.request.url;
        resReq.startTime = request.time;
        resReq.method = request.data.request.method || "";

        this.result.resources.push(resReq);
    }
    this.modifyActionStartTime(request);
};

//Network message indicating that we received a response from the server for a resource.
STDataManager.prototype.netResRecProcessor = function (request) {
    var _methodName = "netResRecProcessor";
    if (!this.result.resources) {
        this.result.resources = [];
    }
    var isExisted = false;
    var resReq;
    for (var i in this.result.resources) {
        resReq = this.result.resources[i];
        if (resReq.id == request.data.requestId) {
            isExisted = true;
            resReq.fromCache = request.data.response.fromDiskCache;
            resReq.mimeType = request.data.response.mimeType;
            resReq.responseStartTime = request.time;
            resReq.requestTime = resReq.responseStartTime - resReq.startTime;
            if(resReq.startTime) {
                resReq.blocked = request.data.response.timing.requestTime - resReq.startTime;
            }
            else {
                resReq.blockedEndTime = request.data.response.timing.requestTime;
            }

            resReq.statusCode = request.data.response.status;

            if(resReq.endTime) {
                resReq.totalTime = resReq.endTime - resReq.responseStartTime;
            }
        }
    }
    if (!isExisted) {
        resReq = {};
        resReq.id = request.data.requestId;
        resReq.url = request.data.response.url;
        resReq.fromCache = request.data.response.fromDiskCache;
        resReq.mimeType = request.data.response.mimeType;
        if(resReq.startTime) {
            resReq.blocked = request.data.response.timing.requestTime - resReq.startTime;
        }
        else {
            resReq.blockedEndTime = request.data.response.timing.requestTime;
        }
        resReq.statusCode = request.data.response.status;
        resReq.responseStartTime = request.time;
        resReq.requestTime = resReq.responseStartTime - resReq.startTime;

        this.result.resources.push(resReq);
    };

    this.modifyActionEndTime(request);

    this.addHeaderStatistics(request);
};

//Network message indicating that we received a response from the server for a resource.
STDataManager.prototype.netDataRecProcessor = function (request) {
    var _methodName = "netDataRecProcessor";
    if (!this.result.resources) {
        this.result.resources = [];
    }

    var isExisted = false;
    var resReq;
    for (var i in this.result.resources) {
        resReq = this.result.resources[i];
        if (resReq.id == request.data.requestId) {
            isExisted = true;
            if(!resReq.totalBytes) {
                resReq.totalBytes = 0;
            }
            resReq.totalBytes += request.data.dataLength;
        }
    }

    if(!isExisted) {
        mstr.$error(_methodName, "Request(id:" + request.data.requestId + ") is not found.");
    }
};

//Javascript Profile information from the Browser
STDataManager.prototype.proDataProcessor = function (request) {};

//Unknown
STDataManager.prototype.unknownProcessor = function (request) {};

//DefaultProcessor
STDataManager.prototype.defaultProcessor = function (request) {
    var _methodName = "defaultProcessor";
    mstr.$error(_methodName, "Should not enter this method.");
};

STDataManager.prototype.process = function (data) {
    var _methodName = "process";
    var datatype = data.type;

    switch (datatype) {
        case 0:
            this.domEventProcessor(data);
            break;
        case 1:
            this.layoutProcessor(data);
            break;
        case 2:
            this.recalStylesProcessor(data);
            break;
        case 3:
            this.paintProcessor(data);
            break;
        case 4:
            this.parseHTMLProcessor(data);
            break;
        case 5:
            this.timerInstallProcessor(data);
            break;
        case 6:
            this.timerClearProcessor(data);
            break;
        case 7:
            this.timerProcessor(data);
            break;
        case 8:
            this.xhrReadyStateChangeProcessor(data);
            break;
        case 9:
            this.xhrLoadProcessor(data);
            break;
        case 10:
            this.evalScriptProcessor(data);
            break;
        case 11:
            this.logEventProcessor(data);
            break;
        case 12:
            this.networkResStartProcessor(data);
            break;
        case 13:
            this.networkResResponseProcessor(data);
            break;
        case 14:
            this.networkResFinishedProcessor(data);
            break;
        case 15:
            this.jsCallbackProcessor(data);
            break;
        case 16:
            this.resDataRecProcessor(data);
            break;
        case 17:
            this.gcEventProcessor(data);
            break;
        case 18:
            this.markDOMContentProcessor(data);
            break;
        case 19:
            this.markLoadEventProcessor(data);
            break;
        case 20:
            this.schResReqProcessor(data);
            break;
        case 0x7FFFFFFE:
        case 2147483646:
        case  - 1:
            this.tabChangedProcessor(data);
            break;
        case 0x7FFFFFFD:
        case 2147483645:
        case  - 2:
            this.netResUpdateProcessor(data);
            break;
        case 0x7FFFFFFC:
        case 2147483644:
        case  - 3:
            this.proDataProcessor(data);
            break;
        case 0x7FFFFFFA:
        case 2147483642:
        case  - 5:
            this.netReqWBSentProcessor(data);
            break;
        case 0x7FFFFFF9:
        case 2147483641:
        case  - 6:
            this.netResRecProcessor(data);
            break;
        case 0x7FFFFFF8:
        case 2147483640:
        case  - 7:
            this.netDataRecProcessor(data);
            break;
        case 0x7FFFFFF7:
        case 2147483639:
        case  - 8:
            this.unknownProcessor(data);
            break;
        default:
            this.defaultProcessor(data);
            break;
    }
};

if(!localStorage["interval"]) {
    localStorage["interval"] = 500;
}
if(!localStorage["samples"]) {
    localStorage["samples"] = 5;
}
function CSPATMonitor() {
    this.isCSPATStarted = false;
    this.isMonitoring = false;
    this.isDataReady = false;
    this.counter = 0;
    this.startCallbackReceived = false;
    this.stdm = new STDataManager();

    this.sample = 0;
    this.currentCpu = 0;
    this.currentNet = 0;
    this.isDetectCpuNet = false;
    this.targetSample = 5;
    this.targetInterval = 500;

    return this;
}
CSPATMonitor.prototype.init = function () {
    this.stdm.reset();
    this.counter = 0;
};

CSPATMonitor.prototype.start = function () {
    this.isCSPATStarted = true;
    this.init();
    chrome.webRequest.onBeforeRequest.addListener(beforeRequestListener, {urls : ["http://*/MicroStrategy*", "https://*/MicroStrategy*"]}/*, ["blocking"]*/);
    chrome.webRequest.onCompleted.addListener(completeListener, {urls : ["http://*/MicroStrategy*", "https://*/MicroStrategy*"]});
};

CSPATMonitor.prototype.stop = function () {
    this.isCSPATStarted = false;
    chrome.webRequest.onBeforeRequest.removeListener(beforeRequestListener);
    chrome.webRequest.onCompleted.removeListener(completeListener);
};

CSPATMonitor.prototype.isCooldown = function() {
    var _methodName = "isCooldown";
    if (this.sample >= localStorage["samples"]) {
        sendMsgtoContentPage({type: MSG_TYPE_GET_DATA});
        this.isDetectCpuNet = false;
        this.isMonitoring = false;
        this.sample = 0;
    }
    if (this.currentCpu <= 0.05 && this.currentNet <= 1 && this.sample < 5 && this.isDetectCpuNet) {
        this.sample++;
        setTimeout((function (that) {
            return function () {
                that.isCooldown();
            };
        })(this), localStorage["interval"]);
    }
};

CSPATMonitor.prototype.dataProcess = function(rawData) {
    this.stdm.dataProcess(rawData);
    this.isDataReady = this.stdm.isStatsReady && this.stdm.isDataReady;
};

CSPATMonitor.prototype.statisticsProcess = function(data) {
    this.stdm.addHtmlStatistics(data);
    this.isDataReady = this.stdm.isStatsReady && this.stdm.isDataReady;
};

CSPATMonitor.prototype.getResult = function () {
    //return this.stdm.getResult();
    var result = this.stdm.getResult();
    var resources = [];
    var firstRequestFound = false;

    for(var i in result.resources) {
        var request = result.resources[i];


        if(!firstRequestFound && request.startTime == undefined) {
            firstRequestFound = true;
            request.startTime = result.statistics.startTime;
            request.requestTime = request.responseStartTime - request.startTime;
            request.totalTime = request.endTime - request.startTime;
            if(request.blockedEndTime) {
                request.blocked = request.blockedEndTime - request.startTime;
            }
        }

        // Reset negative blocked value to zero.
        if(request.blocked < 0) {
            request.blocked = 0;
        }

        for(var name in result.resources[i]) {
            if(name.indexOf("id") != -1 || name.indexOf("fromCache") != -1) {
                continue;
            }
            if(!isNaN(result.resources[i][name]))
            {
                result.resources[i][name] = Math.round(result.resources[i][name]);
            }
        }

        // Del request whose total time is zero.
        if(request.totalTime != 0) {
            resources.push(result.resources[i]);
        }
    }
    result.resources = resources;

    for(var name in result.statistics) {
        if(!isNaN(result.statistics[name]))
        {
            result.statistics[name] = Math.round(result.statistics[name]);
        }
    }

    for(var i in result.js)
    {
        for(var name in result.js[i]) {
            if(!isNaN(result.js[i][name]))
            {
                result.js[i][name] = Math.round(result.js[i][name]);
            }
        }
    }


    return result;
};

CSPATMonitor.prototype.setStartTime = function (startTime) {
    this.stdm.setStartTime(startTime);
};

var cspatMonitor = new CSPATMonitor();
var connections = {};
chrome.runtime.onConnect.addListener(function(port) {
	var portListener = function(message, sender, sendResponse) {
		var _methodName = "portListener";
		switch(message.type) {
            // Start CSPAT, from extension page.
            case MSG_TYPE_START_CSPAT:
                cspatMonitor.start();
                // Forward to content page.
				sendMsgtoContentPage({type: MSG_TYPE_START_CSPAT});
				break;

            // Stop CSPAT, from extension page.
            case MSG_TYPE_STOP_CSPAT:
                cspatMonitor.stop();
                // Forward to content page.
				sendMsgtoContentPage({type: MSG_TYPE_STOP_CSPAT});
				break;

            // Initialize/Update a connection. From content page or extension page.
			case MSG_TYPE_INIT:
				connections[port.name] = port;
                port.postMessage({type: MSG_TYPE_MONITOR_STAT, data:cspatMonitor.isCSPATStarted, isExtensionPageExist: connections[PORT_NAME_EXTENSION_PAGE] != undefined});
				mstr.$debug(_methodName, "Port:" + port.name + " added.");
				break;

            // Print the log info from content/extension page.
			case MSG_TYPE_LOG:
				mstr.$log(_methodName, message.data);
				break;

            // Print debug info from content/extension page.
            case MSG_TYPE_DEBUG:
                mstr.$debug(_methodName, message.data);
                break;

			// Print error info from content/extension page.
			case MSG_TYPE_ERROR:
				mstr.$error(_methodName, message.data);
				break;
				
            // Speedtracer data received from content page.
			case MSG_TYPE_DUMP_DATA:
                cspatMonitor.dataProcess(message.data);
                //mstr.$log(_methodName, message.data);
				if(cspatMonitor.isDataReady) {
                    // Forward to extension page.
					sendMsgtoExtensionPage({type:MSG_TYPE_DUMP_DATA, data:cspatMonitor.getResult()});
                    cspatMonitor.init();
				}
				break;

            // Statistics received from content page.
			case MSG_TYPE_STATS:
                cspatMonitor.statisticsProcess(message.data);
				if(cspatMonitor.isDataReady) {
                    // Forward to extension page.
					sendMsgtoExtensionPage({type:MSG_TYPE_DUMP_DATA, data:cspatMonitor.getResult()});
                    cspatMonitor.init();
				}
				break;

            // From content page, indicate mouse is clicked.
            case MSG_TYPE_CLICKED:
                cspatMonitor.isMonitoring = true;
                break;

			default:
				mstr.$warning(_methodName, "Wrong Message Type:" + message.type);
				break;
		}
	}
	port.onMessage.addListener(portListener);
	port.onDisconnect.addListener(function(port) {
		port.onMessage.removeListener(portListener);
		delete connections[port.name];
        chrome.browserAction.setIcon({"path": "icons/disabled.png"});
	});
});


function beforeRequestListener (details) {
	var _methodName = "beforeRequestListener";
    if(cspatMonitor.isMonitoring) {
        cspatMonitor.isDetectCpuNet = false;
        cspatMonitor.counter++;
    }
}

function completeListener (details) {
	var _methodName = "completeListener";
    if(cspatMonitor.isMonitoring) {
        cspatMonitor.counter--;
    }

    if (cspatMonitor.counter == 0) {
        cspatMonitor.sample = 0;
        cspatMonitor.isDetectCpuNet = true;
        cspatMonitor.isCooldown();
    }
}

function sendMsgtoContentPage(msg) {
	var _methodName = "sendMsgtoContentPage";
	if(connections[PORT_NAME_CONTENT_PAGE]) {
		connections[PORT_NAME_CONTENT_PAGE].postMessage(msg);
	}
	else {
		mstr.$warning(_methodName, "connections[PORT_NAME_CONTENT_PAGE] undefined.");
	}
}

function sendMsgtoExtensionPage(msg) {
	var _methodName = "sendMsgtoExtensionPage";
	if(connections[PORT_NAME_EXTENSION_PAGE]) {
		connections[PORT_NAME_EXTENSION_PAGE].postMessage(msg);
	}
	else {
		mstr.$warning(_methodName, "connections[PORT_NAME_EXTENSION_PAGE] undefined.");
	}
}

chrome.browserAction.onClicked.addListener(function(tab) {
    var url = chrome.extension.getURL("cspat/data_renderer.html");
	if(connections[PORT_NAME_EXTENSION_PAGE]) {
        // TODO set focus.
        sendMsgtoExtensionPage({type: MSG_TYPE_GET_FOCUS});
    }
    else {
        chrome.windows.create({"type": "detached_panel", "height":640, "width":960, "url":url}, function(window) {});
        chrome.browserAction.setIcon({"path":"icons/enabled.png"});
    }

})


